﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//
using System.Collections;
using System.IO;
using System.Net;
/*
Copyright 2011 Mirosław M. Mączka

This file is part of MobiLang.

    MobiLang is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    Foobar is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/
namespace MobiLang
{
    public class MobiHandler
    {
        private enum MobiHdrOffset { identifier, header_length, Mobi_type, text_Encoding, Unique_ID, File_version, Ortographic_index, Inflection_index, Index_names, Index_keys, Extra_index_0, Extra_index_1, Extra_index_2, Extra_index_3, Extra_index_4, Extra_index_5, First_Non_book_index, Full_Name_Offset, Full_Name_Length, Locale, Input_Language, Output_Language, Min_version, First_Image_index, Huffman_Record_Offset, Huffman_Record_Count, Huffman_Table_Offset, Huffman_Table_Length, EXTH_flags, Unknown1, DRM_Offset, DRM_Count, DRM_Size, DRM_Flags, Unknown2, Extra_Data_Flags };
        private long offsetLocale = -1;
        private enum LangOffset {Lang=0, InputLanguage=4, OutputLanguage=8};

        public string FileName
        {
            get;
            set;
        }
        public bool FileOK
        {
            get;
            set;
        }
        public string InternalName
        {
            get;
            set;
        }
        public bool MobiHeaderOK
        {
            get;
            set;
        }
        public string FullName
        {
            get;
            set;
        }
        public int MobiType
        {
            get;
            set;
        }
        public int TextEncoding
        {
            get;
            set;
        }
        public int Locale
        {
            get;
            set;
        }
        public int InputLanguage
        {
            get;
            set;
        }
        public int OutputLanguage
        {
            get;
            set;
        }
        public int ExthFlags
        {
            get;
            set;
        }

        public bool ExthHeaderOK
        {
            get;
            set;
        }
        public List<string> Exths = new List<string>();
        //public Hashtable exth
        //{ get; set; }
        public string Author
        {
            get;
            set;
        }
        public string Title
        {
            get;
            set;
        }
        public MobiHandler()
        {
        }

        public bool ReadHeader()
        {
            //
            FileOK = false;
            InternalName = "";
            MobiHeaderOK = false;
            MobiType = 0;
            TextEncoding = 0;
            Locale = 0;
            InputLanguage = 0;
            OutputLanguage = 0;
            ExthFlags = 0;
            ExthHeaderOK = false;
            Exths = new List<string>();
            //exth = new Hashtable();
            //
            offsetLocale = -1;
            Author = "";
            Title = "";
            FullName = "";
            //
            int[] ibuff = new int[60];
            //
            //LanguageCodes lc = new LanguageCodes();
            CETHX cethx = new CETHX();
            FileStream f = null;
            BinaryReader br = null;
            //
            try
            {
                f = File.OpenRead(FileName);
                br = new BinaryReader(f);
                //
                string header = "File Name: [" + Path.GetFileName(FileName) + "]";
                Console.WriteLine("\n".PadRight(header.Length+1, '='));
                Console.WriteLine(header);
                //
                f.Seek(0, SeekOrigin.Begin);
                {
                    byte[] bb = new byte[32];
                    f.Read(bb, 0, bb.Length);
                    Console.WriteLine(string.Format("Internal name: [{0}]", InternalName=cethx.Cnv(typeof(string), bb)));

                }
                //
                f.Seek(76, SeekOrigin.Begin);
                short n1 = IPAddress.NetworkToHostOrder(br.ReadInt16());
                int mobiOffset = IPAddress.NetworkToHostOrder(br.ReadInt32());
                mobiOffset += 16;//HACK 
                //
                //
                //f.Seek(mobiOffset, SeekOrigin.Begin);
                //f.Read(buff, 0, buff.Length);
                //
                //
                f.Seek(mobiOffset, SeekOrigin.Begin);
                int hdrMOBIMagic = IPAddress.NetworkToHostOrder(br.ReadInt32());//1297039945
                int hdrMOBILength = IPAddress.NetworkToHostOrder(br.ReadInt32());
                //
                if (1297039945 != hdrMOBIMagic)
                {
                    //Console.WriteLine("ERROR: Can't find MOBI HEADER!");
                    throw new Exception("Can't find MOBI HEADER!");
                }
                MobiHeaderOK = true;
                Console.WriteLine("\nMOBI HEADER:");
                //
                f.Seek(mobiOffset, SeekOrigin.Begin);
                for (int i = 0; i < ibuff.Length; i++)
                {
                    if ((int)MobiHdrOffset.Locale == i)
                    {
                        offsetLocale = f.Position;
                    }
                    ibuff[i] = IPAddress.NetworkToHostOrder(br.ReadInt32());
                }
                Console.WriteLine("Mobi_type: {0}", ClMobiType.ToText(MobiType = ibuff[(int)MobiHdrOffset.Mobi_type]));
                Console.WriteLine("Text_Encoding: {0}", CCP.ToText(TextEncoding = ibuff[(int)MobiHdrOffset.text_Encoding]));
                Console.WriteLine("Locale: {0}", LanguageCodes.ToText(Locale = ibuff[(int)MobiHdrOffset.Locale]));
                Console.WriteLine("Input_Language: {0}", LanguageCodes.ToText(InputLanguage = ibuff[(int)MobiHdrOffset.Input_Language]));
                Console.WriteLine("Output_Language: {0}", LanguageCodes.ToText(OutputLanguage = ibuff[(int)MobiHdrOffset.Output_Language]));
                //Console.WriteLine("Full_Name_Offset={0}", ibuff[(int)MobiHdrOffset.Full_Name_Offset]);
                //Console.WriteLine("Full_Name_Length={0}", ibuff[(int)MobiHdrOffset.Full_Name_Length]);
                if ((mobiOffset + ibuff[(int)MobiHdrOffset.Full_Name_Offset] - 16)>=0)//HACK
                {
                    f.Seek(mobiOffset + ibuff[(int)MobiHdrOffset.Full_Name_Offset] - 16, SeekOrigin.Begin);
                    byte[] bb = br.ReadBytes(ibuff[(int)MobiHdrOffset.Full_Name_Length]);
                    FullName = cethx.Cnv(typeof(string), bb);
                    Console.WriteLine(string.Format("FullName: [{0}]", FullName));
                }
                else Console.WriteLine("Full_name: Unknown!");
                Console.WriteLine("EXTH_flags: 0x{0:X}", ibuff[ExthFlags=(int)MobiHdrOffset.EXTH_flags]);
                //
                int mobi_length = ibuff[(int)MobiHdrOffset.header_length];
                //f.Seek(mobiOffset + mobi_length, SeekOrigin.Begin);
                //
                f.Seek(mobiOffset + hdrMOBILength, SeekOrigin.Begin);
                int hdrEXTHMagic = IPAddress.NetworkToHostOrder(br.ReadInt32());//1163416648
                int hdrEXTHLength = IPAddress.NetworkToHostOrder(br.ReadInt32());
                int hdrEXTHRecords = IPAddress.NetworkToHostOrder(br.ReadInt32());
                //Console.WriteLine("{0:X},{1},{2}", hdrEXTHMagic, hdrEXTHLength, hdrEXTHRecords);
                if (1163416648 != hdrEXTHMagic)
                {
                    throw new Exception("Can't find EXTH HEADER!");
                }
                ExthHeaderOK = true;
                Console.WriteLine("\nEXTH HEADER:");
                //
                for (int ii = 0; ii < hdrEXTHRecords; ii++)
                {
                    int EXTHRecordType = IPAddress.NetworkToHostOrder(br.ReadInt32());
                    int EXTHRecordLength = IPAddress.NetworkToHostOrder(br.ReadInt32());
                    byte[] bb = br.ReadBytes(EXTHRecordLength - 8);
                    if (100 == EXTHRecordType)
                    {
                        Author = cethx.Cnv(typeof(string), bb);
                    }
                    else if (503 == EXTHRecordType)
                    {
                        Title = cethx.Cnv(typeof(string), bb);
                    }
                    Exths.Add(cethx.ToText(EXTHRecordType, EXTHRecordLength, bb));
                    //exth.Add(EXTHRecordType, cethx.ToText(EXTHRecordType, EXTHRecordLength, bb));
                    //Console.WriteLine("{0}/{1}/{2}", EXTHRecordType, EXTHRecordLength, (char)bb[0]);
                    Console.WriteLine(cethx.ToText(EXTHRecordType, EXTHRecordLength, bb));
                }
                Console.WriteLine("\nEND.");
                //
                FileOK = true;
                //
            }
            catch (Exception e)
            {
                //
                FileOK = false;
                //
                offsetLocale = -1;
                Author = "";
                Title = "";
                FullName = "";
                //
                Console.WriteLine(string.Format("ERROR: {0}!", e.Message));
                return false;
            }
            finally
            {
                if (null != br) { br.Close(); br = null; }
                if (null != f) { f.Close(); f = null; }
            }
            //
            return true;
        }

        public bool SetLangLocal(int Lcid)
        {
            return SetLang((int)LangOffset.Lang, Lcid);
        }

        public bool SetLangInputLanguage(int Lcid)
        {
            return SetLang((int)LangOffset.InputLanguage, Lcid);
        }

        public bool SetLangOutputLanguage(int Lcid)
        {
            return SetLang((int)LangOffset.OutputLanguage, Lcid);
        }

        private bool SetLang(int offset, int lcid)
        {
            if (offsetLocale < 0) return false;
            //
            try
            {
                using (FileStream fs = new FileStream(FileName, FileMode.Open))
                {
                    using (BinaryWriter bw = new BinaryWriter(fs))
                    {
                        bw.BaseStream.Seek(offsetLocale + offset, SeekOrigin.Begin);
                        bw.Write((int)IPAddress.HostToNetworkOrder(lcid));
                        bw.Flush();
                    }
                }

            }
            catch (Exception e)
            {
                
                Console.WriteLine("ERROR: file [{0}] - {1}", FileName, e.Message);
            }
            //
            return true;
        }
        public bool RenameFile()
        {
            //
            string to = Utils.CleanFileName(FullName);
            if ("" == to)
            {
                Console.WriteLine("ERROR: Rename - \"\" == FullName");
                return false;
            }
            string path = Path.GetDirectoryName(FileName);
            to = Path.Combine(path, to) + Path.GetExtension(FileName);
            //
            if (FullName == to)
            {
                Console.WriteLine("ERROR: Rename - FileName == FullName");
                return true;
            }
            //
            try
            {
                File.Move(FileName, to);
                Console.WriteLine("Rename [{0}]=>[{1}]", FileName, to);
            }
            catch (Exception e)
            {
                Console.WriteLine("ERROR: rename({0}=>{1}) - {2}", FileName, to, e.Message);
                return false;
            }
            //
            return true;
        }
    }//class
}//namespace
